/**
 * @author See Contributors.txt for code contributors and overview of BadgerDB.
 *
 * @section LICENSE
 * Copyright (c) 2012 Database Group, Computer Sciences Department, University of Wisconsin-Madison.
 */

#include <memory>
#include <iostream>
#include "buffer.h"
#include "exceptions/buffer_exceeded_exception.h"
#include "exceptions/page_not_pinned_exception.h"
#include "exceptions/page_pinned_exception.h"
#include "exceptions/bad_buffer_exception.h"
#include "exceptions/hash_not_found_exception.h"

namespace badgerdb
{

	BufMgr::BufMgr(std::uint32_t bufs)
		: numBufs(bufs)
	{
		bufDescTable = new BufDesc[bufs];

		for (FrameId i = 0; i < bufs; i++)
		{
			bufDescTable[i].frameNo = i;
			bufDescTable[i].valid = false;
		}

		bufPool = new Page[bufs];

		int htsize = ((((int)(bufs * 1.2)) * 2) / 2) + 1;
		hashTable = new BufHashTbl(htsize); // allocate the buffer hash table

		clockHand = bufs - 1;
	}

	BufMgr::~BufMgr()
	{

		BufDesc *tmpbuf;
		for (std::uint32_t i = 0; i < numBufs; i++)
		{
			tmpbuf = &(bufDescTable[i]);
			if (tmpbuf->dirty)
				tmpbuf->file->writePage(bufPool[tmpbuf->pageNo]);
		}
		delete hashTable;
		delete[] bufPool;
		delete[] bufDescTable;
	}

	void BufMgr::advanceClock()
	{
		clockHand = (clockHand + 1) % numBufs;
	}

	void BufMgr::allocBuf(FrameId &frame)
	{
		/**
		 * 记录被pin的页的个数
		 */
		std::uint32_t count = 0;
		while (true)
		{

			advanceClock();
			//有效
			if (!bufDescTable[clockHand].valid)
			{
				frame = clockHand;
				return;
			}
			// pinCnt >0
			if (bufDescTable[clockHand].pinCnt)
			{
				bufDescTable[clockHand].refbit = false;
				count++;
				//全部pin住
				if (count == numBufs)
					throw BufferExceededException();
				else
					continue;
			}
			// refbit == true
			if (bufDescTable[clockHand].refbit)
			{
				bufDescTable[clockHand].refbit = false;
				continue;
			}
			// dirty == true
			if (bufDescTable[clockHand].dirty)
			{
				bufDescTable[clockHand].file->writePage(bufPool[clockHand]);
				bufDescTable[clockHand].dirty = false;
			}
			try
			{
				FrameId frameNo;
				hashTable->lookup(bufDescTable[clockHand].file, bufDescTable[clockHand].pageNo, frameNo);
				hashTable->remove(bufDescTable[clockHand].file, bufDescTable[clockHand].pageNo);
			}
			catch (HashNotFoundException &e)
			{
			}
			frame = clockHand;
			return;
		}
	}

	void BufMgr::readPage(File *file, const PageId pageNo, Page *&page)
	{
		FrameId frameNo;
		try
		{
			hashTable->lookup(file, pageNo, frameNo);//存在
			bufDescTable[frameNo].refbit = true;
			bufDescTable[frameNo].pinCnt++;
			page = &(bufPool[frameNo]);//直接返回
		}
		catch (HashNotFoundException &e)
		{
			allocBuf(frameNo);//分配新页框
			bufPool[frameNo] = file->readPage(pageNo);//读页面，放入
			page = &(bufPool[frameNo]);//返回
			hashTable->insert(file, pageNo, frameNo);//插入hash表
			bufDescTable[frameNo].Set(file, pageNo);
		}
	}

	void BufMgr::unPinPage(File *file, const PageId pageNo, const bool dirty)
	{
		FrameId frameNo;
		try
		{
			hashTable->lookup(file, pageNo, frameNo);
			if (!bufDescTable[frameNo].pinCnt)//已经不被pin了
				throw PageNotPinnedException(bufDescTable[frameNo].file->filename(), bufDescTable[frameNo].pageNo, frameNo);
			bufDescTable[frameNo].pinCnt--;
			if (dirty)//设置dirty
				bufDescTable[frameNo].dirty = true;
		}
		catch (HashNotFoundException &e)
		{
		}
	}

	void BufMgr::flushFile(const File *file)
	{
		BufDesc *tmpbuf;
		for (std::uint32_t i = 0; i < numBufs; i++)
		{
			tmpbuf = &(bufDescTable[i]);
			if (tmpbuf->file != file)
				continue;
			if (tmpbuf->pinCnt)
				throw PagePinnedException(file->filename(), tmpbuf->pageNo, tmpbuf->frameNo);
			if (!tmpbuf->valid)
				throw BadBufferException(tmpbuf->frameNo, tmpbuf->dirty, tmpbuf->valid, tmpbuf->refbit);
			if (tmpbuf->dirty)
			{
				tmpbuf->file->writePage(bufPool[i]);
				tmpbuf->dirty = false;
			}
			hashTable->remove(tmpbuf->file, tmpbuf->pageNo);
			tmpbuf->Clear();
		}
	}

	void BufMgr::allocPage(File *file, PageId &pageNo, Page *&page)
	{
		FrameId frameNo;
		allocBuf(frameNo);
		bufPool[frameNo] = file->allocatePage();
		page = &(bufPool[frameNo]);
		pageNo = bufPool[frameNo].page_number();
		hashTable->insert(file, pageNo, frameNo);
		bufDescTable[frameNo].Set(file, pageNo);
	}

	void BufMgr::disposePage(File *file, const PageId PageNo)
	{
		FrameId frameNo;
		try
		{
			hashTable->lookup(file, PageNo, frameNo);
			bufDescTable[frameNo].Clear();
			hashTable->remove(file, PageNo);
		}
		catch (HashNotFoundException &e)
		{
		}
		file->deletePage(PageNo);
	}

	void BufMgr::printSelf(void)
	{
		BufDesc *tmpbuf;
		int validFrames = 0;

		for (std::uint32_t i = 0; i < numBufs; i++)
		{
			tmpbuf = &(bufDescTable[i]);
			std::cout << "FrameNo:" << i << " ";
			tmpbuf->Print();

			if (tmpbuf->valid == true)
				validFrames++;
		}

		std::cout << "Total Number of Valid Frames:" << validFrames << "\n";
	}

}
